﻿using System.Collections.Generic;
using System.IO;
using LoungeRepo.Contracts.Core;
using LoungeRepo.Contracts.EntityCache;
using LoungeRepo.Contracts.EntityStore;
using LoungeRepo.Contracts.Normalizer;
using LoungeRepo.Contracts.Serialization;
using LoungeRepo.DataStructures;
using LoungeRepo.EntityCache;
using LoungeRepo.EntityStore;
using LoungeRepo.Normalizer;
using LoungeRepo.Serialization;

namespace LoungeRepo.Core
{
    public partial class LoungeRepository : ILoungeRepository
    {
        public const string DEFAULT_LOUNGE_REPOSITORY_ROOT_FOLDER_NAME = "LoungeRepository.Root";
        public const string DEFAULT_PARTITION_NAME = "~Default";

        private readonly string rootFolderPath;

        private readonly INormalizedEntitySerializer serializer;
        private readonly IEntityStore filesys;
        private readonly IEntityCache cache;


        public LoungeRepository() : this(DEFAULT_LOUNGE_REPOSITORY_ROOT_FOLDER_NAME) {}
        public LoungeRepository(string configString)
        {
            this.rootFolderPath = Path.GetFullPath(configString);

            this.serializer = new BinarySerializer();
            this.filesys = new FilesystemEntityStore(configString);
            this.cache = new SecondLevelCache();
        }


        public void Store(object nonEntityRoot, string id) { Store(nonEntityRoot, id, DEFAULT_PARTITION_NAME); }
        public void Store(object nonEntityRoot, string id, string partition)
        {
            var entity = new WrapperEntity {Id = id, Partition = partition, NonEntityObject = nonEntityRoot};
            Store(entity);
        }
        public void Store(object entityRoot)
        {
            var normalizer = new EntityNormalizer(OnResolveEntityReferenceRequest, OnEntityReportedDuringDeNormalization);

            ILoungeRepoEntityIdentity entityIdentity;
            if (normalizer.TryExtractEntityIdentity(entityRoot, out entityIdentity))
            {
                var normalizedEntities = normalizer.Normalize(entityRoot);
                PersistNormalizedEntities(normalizedEntities);
            }
        }


        public T Load<T>(string id) { return Load<T>(id, DEFAULT_PARTITION_NAME); }
        public T Load<T>(string id, string partition) { return (T) Load(id, partition); }
        public object Load(string id, string partition)
        {
            object entity;
            if (this.cache.TryGetByKey(id, partition, out entity))
                return entity;

            INormalizedEntity normalizedEntity = DepersistNormalizedEntity(id, partition);

            var normalizer = new EntityNormalizer(OnResolveEntityReferenceRequest, OnEntityReportedDuringDeNormalization);
            entity = normalizer.Denormalize(normalizedEntity);

            if (entity is WrapperEntity)
                entity = (entity as WrapperEntity).NonEntityObject;

            return entity;
        }


        public void Delete(string id) { Delete(id, DEFAULT_PARTITION_NAME);  }
        public void Delete(string id, string partition)
        {
            this.cache.Evict(id, partition);
            this.filesys.Delete(id, partition);
        }


        public IEnumerable<T> GetExtent<T>()
        {
            return this.cache.GetByType<T>();
        }


        public string RootFolderPath
        {
            get
            {
                return this.rootFolderPath;
            }
        }


        private object OnResolveEntityReferenceRequest(ILoungeRepoEntityIdentity entityReference)
        {
            object entity;
            if (this.cache.TryGetByKey(entityReference.Id, entityReference.Partition, out entity))
                return entity;

            return DepersistNormalizedEntity(entityReference.Id, entityReference.Partition);
        }


        private void OnEntityReportedDuringDeNormalization(object entity, ILoungeRepoEntityIdentity entityIdentity)
        {
            if (entity is WrapperEntity) entity = (entity as WrapperEntity).NonEntityObject;
            this.cache.Register(entity, entityIdentity.Id, entityIdentity.Partition);
        }


        #region Implementation of IDisposable
        public void Dispose()
        {}
        #endregion
    }
}
